home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / gnu / cvs-1_3.lha / cvs-1.3 / src / commit.c < prev    next >
C/C++ Source or Header  |  1992-03-31  |  30KB  |  1,230 lines

  1. /*
  2.  * Copyright (c) 1992, Brian Berliner and Jeff Polk
  3.  * Copyright (c) 1989-1992, Brian Berliner
  4.  * 
  5.  * You may distribute under the terms of the GNU General Public License as
  6.  * specified in the README file that comes with the CVS 1.3 kit.
  7.  * 
  8.  * Commit Files
  9.  * 
  10.  * "commit" commits the present version to the RCS repository, AFTER
  11.  * having done a test on conflicts.
  12.  *
  13.  * The call is: cvs commit [options] files...
  14.  * 
  15.  */
  16.  
  17. #include "cvs.h"
  18.  
  19. #ifndef lint
  20. static char rcsid[] = "@(#)commit.c 1.84 92/03/31";
  21. #endif
  22.  
  23. #if __STDC__
  24. static Dtype check_direntproc (char *dir, char *repos, char *update_dir);
  25. static int check_fileproc (char *file, char *update_dir, char *repository,
  26.                List * entries, List * srcfiles);
  27. static int check_filesdoneproc (int err, char *repos, char *update_dir);
  28. static int checkaddfile (char *file, char *repository, char *tag);
  29. static Dtype commit_direntproc (char *dir, char *repos, char *update_dir);
  30. static int commit_dirleaveproc (char *dir, int err, char *update_dir);
  31. static int commit_fileproc (char *file, char *update_dir, char *repository,
  32.                 List * entries, List * srcfiles);
  33. static int commit_filesdoneproc (int err, char *repository, char *update_dir);
  34. static int finaladd (char *file, char *revision, char *tag, char *repository,
  35.              List *entries);
  36. static int findmaxrev (Node * p);
  37. static int fsortcmp (Node * p, Node * q);
  38. static int lock_RCS (char *user, char *rcs, char *rev, char *repository);
  39. static int lock_filesdoneproc (int err, char *repository, char *update_dir);
  40. static int lockrcsfile (char *file, char *repository, char *rev);
  41. static int precommit_list_proc (Node * p);
  42. static int precommit_proc (char *repository, char *filter);
  43. static int remove_file (char *file, char *repository, char *tag,
  44.             List *entries);
  45. static void fix_rcs_modes (char *rcs, char *user);
  46. static void fixaddfile (char *file, char *repository);
  47. static void fixbranch (char *file, char *repository, char *branch);
  48. static void unlockrcs (char *file, char *repository);
  49. static void ci_delproc (Node *p);
  50. static void locate_rcs (char *file, char *repository, char *rcs);
  51. #else
  52. static int fsortcmp ();
  53. static int lock_filesdoneproc ();
  54. static int check_fileproc ();
  55. static Dtype check_direntproc ();
  56. static int precommit_list_proc ();
  57. static int precommit_proc ();
  58. static int check_filesdoneproc ();
  59. static int commit_fileproc ();
  60. static int commit_filesdoneproc ();
  61. static Dtype commit_direntproc ();
  62. static int commit_dirleaveproc ();
  63. static int findmaxrev ();
  64. static int remove_file ();
  65. static int finaladd ();
  66. static void unlockrcs ();
  67. static void fixaddfile ();
  68. static void fixbranch ();
  69. static int checkaddfile ();
  70. static int lockrcsfile ();
  71. static int lock_RCS ();
  72. static void fix_rcs_modes ();
  73. static void ci_delproc ();
  74. static void locate_rcs ();
  75. #endif                /* __STDC__ */
  76.  
  77. struct commit_info
  78. {
  79.     Ctype status;            /* as returned from Classify_File() */
  80.     char *rev;                /* a numeric rev, if we know it */
  81.     char *tag;                /* any sticky tag, or -r option */
  82. };
  83. struct master_lists
  84. {
  85.     List *ulist;            /* list for Update_Logfile */
  86.     List *cilist;            /* list with commit_info structs */
  87. };
  88.  
  89. static int got_message;
  90. static int run_module_prog = 1;
  91. static int aflag;
  92. static char *tag;
  93. static char *write_dirtag;
  94. static char *logfile;
  95. static List *mulist;
  96. static List *locklist;
  97. static char *message;
  98.  
  99. static char *commit_usage[] =
  100. {
  101.     "Usage: %s %s [-nRl] [-m msg | -f logfile] [-r rev] files...\n",
  102.     "\t-n\tDo not run the module program (if any).\n",
  103.     "\t-R\tProcess directories recursively.\n",
  104.     "\t-l\tLocal directory only (not recursive).\n",
  105.     "\t-f file\tRead the log message from file.\n",
  106.     "\t-m msg\tLog message.\n",
  107.     "\t-r rev\tCommit to this branch or trunk revision.\n",
  108.     NULL
  109. };
  110.  
  111. int
  112. commit (argc, argv)
  113.     int argc;
  114.     char *argv[];
  115. {
  116.     int c;
  117.     int err = 0;
  118.     int local = 0;
  119.  
  120.     if (argc == -1)
  121.     usage (commit_usage);
  122.  
  123. #ifdef CVS_BADROOT
  124.     /*
  125.      * For log purposes, do not allow "root" to commit files.  If you look
  126.      * like root, but are really logged in as a non-root user, it's OK.
  127.      */
  128.     if (geteuid () == (uid_t) 0)
  129.     {
  130.     struct passwd *pw;
  131.  
  132.     if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
  133.         error (1, 0, "you are unknown to this system");
  134.     if (pw->pw_uid == (uid_t) 0)
  135.         error (1, 0, "cannot commit files as 'root'");
  136.     }
  137. #endif /* CVS_BADROOT */
  138.  
  139.     message = xmalloc (MAXMESGLEN + 1);
  140.     message[0] = '\0';            /* Null message by default */
  141.     optind = 1;
  142.     while ((c = gnu_getopt (argc, argv, "nlRm:f:r:")) != -1)
  143.     {
  144.     switch (c)
  145.     {
  146.         case 'n':
  147.         run_module_prog = 0;
  148.         break;
  149.         case 'm':
  150. #ifdef FORCE_USE_EDITOR
  151.         use_editor = TRUE;
  152. #else
  153.         use_editor = FALSE;
  154. #endif
  155.         if (strlen (optarg) >= (size_t) MAXMESGLEN)
  156.         {
  157.             error (0, 0, "warning: message too long; truncated!");
  158.             (void) strncpy (message, optarg, MAXMESGLEN);
  159.             message[MAXMESGLEN] = '\0';
  160.         }
  161.         else
  162.             (void) strcpy (message, optarg);
  163.         break;
  164.         case 'r':
  165.         if (tag)
  166.             free (tag);
  167.         tag = xstrdup (optarg);
  168.         break;
  169.         case 'l':
  170.         local = 1;
  171.         break;
  172.         case 'R':
  173.         local = 0;
  174.         break;
  175.         case 'f':
  176. #ifdef FORCE_USE_EDITOR
  177.         use_editor = TRUE;
  178. #else
  179.         use_editor = FALSE;
  180. #endif
  181.         logfile = optarg;
  182.         break;
  183.         case '?':
  184.         default:
  185.         usage (commit_usage);
  186.         break;
  187.     }
  188.     }
  189.     argc -= optind;
  190.     argv += optind;
  191.  
  192.     /* numeric specified revision means we ignore sticky tags... */
  193.     if (tag && isdigit (*tag))
  194.     {
  195.     aflag = 1;
  196.     /* strip trailing dots */
  197.     while (tag[strlen (tag) - 1] == '.')
  198.         tag[strlen (tag) - 1] = '\0';
  199.     }
  200.  
  201.     /* some checks related to the "-f logfile" option */
  202.     if (logfile)
  203.     {
  204.     int n, logfd;
  205.  
  206.     if (*message)
  207.         error (1, 0, "cannot specify both a message and a log file");
  208.  
  209.     if ((logfd = open (logfile, O_RDONLY)) < 0 ||
  210.         (n = read (logfd, message, MAXMESGLEN)) < 0)
  211.     {
  212.         error (1, errno, "cannot read log message from %s", logfile);
  213.     }
  214.     (void) close (logfd);
  215.     message[n] = '\0';
  216.     }
  217.  
  218.     /* XXX - this is not the perfect check for this */
  219.     if (argc <= 0)
  220.     write_dirtag = tag;
  221.  
  222.     /*
  223.      * Run the recursion processor to find all the dirs to lock and lock all
  224.      * the dirs
  225.      */
  226.     locklist = getlist ();
  227.     err = start_recursion ((int (*) ()) NULL, lock_filesdoneproc,
  228.                (Dtype (*) ()) NULL, (int (*) ()) NULL, argc,
  229.                argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0);
  230.     sortlist (locklist, fsortcmp);
  231.     if (Writer_Lock (locklist) != 0)
  232.     error (1, 0, "lock failed - giving up");
  233.  
  234.     /*
  235.      * Set up the master update list
  236.      */
  237.     mulist = getlist ();
  238.  
  239.     /*
  240.      * Run the recursion processor to verify the files are all up-to-date
  241.      */
  242.     err = start_recursion (check_fileproc, check_filesdoneproc,
  243.                check_direntproc, (int (*) ()) NULL, argc,
  244.                argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1);
  245.     if (err)
  246.     {
  247.     Lock_Cleanup ();
  248.     error (1, 0, "correct above errors first!");
  249.     }
  250.  
  251.     /*
  252.      * Run the recursion processor to commit the files
  253.      */
  254.     if (noexec == 0)
  255.     err = start_recursion (commit_fileproc, commit_filesdoneproc,
  256.                    commit_direntproc, commit_dirleaveproc,
  257.                    argc, argv, local, W_LOCAL, aflag, 0,
  258.                    (char *) NULL, 1);
  259.  
  260.     /*
  261.      * Unlock all the dirs and clean up
  262.      */
  263.     Lock_Cleanup ();
  264.     dellist (&mulist);
  265.     dellist (&locklist);
  266.     return (err);
  267. }
  268.  
  269. /*
  270.  * compare two lock list nodes (for sort)
  271.  */
  272. static int
  273. fsortcmp (p, q)
  274.     Node *p, *q;
  275. {
  276.     return (strcmp (p->key, q->key));
  277. }
  278.  
  279. /*
  280.  * Create a list of repositories to lock
  281.  */
  282. /* ARGSUSED */
  283. static int
  284. lock_filesdoneproc (err, repository, update_dir)
  285.     int err;
  286.     char *repository;
  287.     char *update_dir;
  288. {
  289.     Node *p;
  290.  
  291.     p = getnode ();
  292.     p->type = LOCK;
  293.     p->key = xstrdup (repository);
  294.     if (p->key == NULL || addnode (locklist, p) != 0)
  295.     freenode (p);
  296.     return (err);
  297. }
  298.  
  299. /*
  300.  * Check to see if a file is ok to commit and make sure all files are
  301.  * up-to-date
  302.  */
  303. /* ARGSUSED */
  304. static int
  305. check_fileproc (file, update_dir, repository, entries, srcfiles)
  306.     char *file;
  307.     char *update_dir;
  308.     char *repository;
  309.     List *entries;
  310.     List *srcfiles;
  311. {
  312.     Ctype status;
  313.     char *xdir;
  314.     Node *p;
  315.     List *ulist, *cilist;
  316.     Vers_TS *vers;
  317.     struct commit_info *ci;
  318.     int save_noexec, save_quiet, save_really_quiet;
  319.  
  320.     save_noexec = noexec;
  321.     save_quiet = quiet;
  322.     save_really_quiet = really_quiet;
  323.     noexec = quiet = really_quiet = 1;
  324.  
  325.     /* handle specified numeric revision specially */
  326.     if (tag && isdigit (*tag))
  327.     {
  328.     /* If the tag is for the trunk, make sure we're at the head */
  329.     if (numdots (tag) < 2)
  330.     {
  331.         status = Classify_File (file, (char *) NULL, (char *) NULL,
  332.                     (char *) NULL, 1, aflag, repository,
  333.                     entries, srcfiles, &vers);
  334.         if (status == T_UPTODATE)
  335.         {
  336.         freevers_ts (&vers);
  337.         status = Classify_File (file, tag, (char *) NULL,
  338.                     (char *) NULL, 1, aflag, repository,
  339.                     entries, srcfiles, &vers);
  340.         if (status == T_REMOVE_ENTRY)
  341.             status = T_MODIFIED;
  342.         }
  343.     }
  344.     else
  345.     {
  346.         char *xtag, *cp;
  347.  
  348.         /*
  349.          * The revision is off the main trunk; make sure we're
  350.          * up-to-date with the head of the specified branch.
  351.          */
  352.         xtag = xstrdup (tag);
  353.         if ((numdots (xtag) & 1) != 0)
  354.         {
  355.         cp = rindex (xtag, '.');
  356.         *cp = '\0';
  357.         }
  358.         status = Classify_File (file, xtag, (char *) NULL,
  359.                     (char *) NULL, 1, aflag, repository,
  360.                     entries, srcfiles, &vers);
  361.         if ((status == T_REMOVE_ENTRY || status == T_CONFLICT)
  362.         && (cp = rindex (xtag, '.')) != NULL)
  363.         {
  364.         /* pluck one more dot off the revision */
  365.         *cp = '\0';
  366.         freevers_ts (&vers);
  367.         status = Classify_File (file, xtag, (char *) NULL,
  368.                     (char *) NULL, 1, aflag, repository,
  369.                     entries, srcfiles, &vers);
  370.         if (status == T_UPTODATE || status == T_REMOVE_ENTRY)
  371.             status = T_MODIFIED;
  372.         }
  373.         /* now, muck with vers to make the tag correct */
  374.         free (vers->tag);
  375.         vers->tag = xstrdup (tag);
  376.         free (xtag);
  377.     }
  378.     }
  379.     else
  380.     status = Classify_File (file, tag, (char *) NULL, (char *) NULL,
  381.                 1, 0, repository, entries, srcfiles, &vers);
  382.     noexec = save_noexec;
  383.     quiet = save_quiet;
  384.     really_quiet = save_really_quiet;
  385.  
  386.     switch (status)
  387.     {
  388.     case T_CHECKOUT:
  389.     case T_NEEDS_MERGE:
  390.     case T_CONFLICT:
  391.     case T_REMOVE_ENTRY:
  392.         error (0, 0, "Up-to-date check failed for `%s'", file);
  393.         freevers_ts (&vers);
  394.         return (1);
  395.     case T_MODIFIED:
  396.     case T_ADDED:
  397.     case T_REMOVED:
  398.         /*
  399.          * some quick sanity checks; if no numeric -r option specified:
  400.          *    - can't have a sticky date
  401.          *    - can't have a sticky tag that is not a branch
  402.          * Also,
  403.          *    - if status is T_REMOVED, can't have a numeric tag
  404.          *    - if status is T_ADDED, rcs file must not exist
  405.          *    - if status is T_ADDED, can't have a non-trunk numeric rev
  406.          */
  407.         if (!tag || !isdigit (*tag))
  408.         {
  409.         if (vers->date)
  410.         {
  411.             error (0, 0,
  412.                "cannot commit with sticky date for file `%s'",
  413.                file);
  414.             freevers_ts (&vers);
  415.             return (1);
  416.         }
  417.         if (status == T_MODIFIED && vers->tag &&
  418.             !RCS_isbranch (file, vers->tag, srcfiles))
  419.         {
  420.             error (0, 0,
  421.                "sticky tag `%s' for file `%s' is not a branch",
  422.                vers->tag, file);
  423.             freevers_ts (&vers);
  424.             return (1);
  425.         }
  426.         }
  427.         if (status == T_REMOVED && vers->tag && isdigit (*vers->tag))
  428.         {
  429.         error (0, 0,
  430.     "cannot remove file `%s' which has a numeric sticky tag of `%s'",
  431.                file, vers->tag);
  432.         freevers_ts (&vers);
  433.         return (1);
  434.         }
  435.         if (status == T_ADDED)
  436.         {
  437.         char rcs[PATH_MAX];
  438.  
  439.         locate_rcs (file, repository, rcs);
  440.         if (isreadable (rcs))
  441.         {
  442.             error (0, 0,
  443.         "cannot add file `%s' when RCS file `%s' already exists",
  444.                file, rcs);
  445.             freevers_ts (&vers);
  446.             return (1);
  447.         }
  448.         if (vers->tag && isdigit (*vers->tag) &&
  449.             numdots (vers->tag) > 1)
  450.         {
  451.             error (0, 0,
  452.         "cannot add file `%s' with revision `%s'; must be on trunk",
  453.                file, vers->tag);
  454.             freevers_ts (&vers);
  455.             return (1);
  456.         }
  457.         }
  458.  
  459.         /* done with consistency checks; now, to get on with the commit */
  460.         if (update_dir[0] == '\0')
  461.         xdir = ".";
  462.         else
  463.         xdir = update_dir;
  464.         if ((p = findnode (mulist, xdir)) != NULL)
  465.         {
  466.         ulist = ((struct master_lists *) p->data)->ulist;
  467.         cilist = ((struct master_lists *) p->data)->cilist;
  468.         }
  469.         else
  470.         {
  471.         struct master_lists *ml;
  472.  
  473.         ulist = getlist ();
  474.         cilist = getlist ();
  475.         p = getnode ();
  476.         p->key = xstrdup (xdir);
  477.         p->type = UPDATE;
  478.         ml = (struct master_lists *)
  479.             xmalloc (sizeof (struct master_lists));
  480.         ml->ulist = ulist;
  481.         ml->cilist = cilist;
  482.         p->data = (char *) ml;
  483.         (void) addnode (mulist, p);
  484.         }
  485.  
  486.         /* first do ulist, then cilist */
  487.         p = getnode ();
  488.         p->key = xstrdup (file);
  489.         p->type = UPDATE;
  490.         p->delproc = update_delproc;
  491.         p->data = (char *) status;
  492.         (void) addnode (ulist, p);
  493.  
  494.         p = getnode ();
  495.         p->key = xstrdup (file);
  496.         p->type = UPDATE;
  497.         p->delproc = ci_delproc;
  498.         ci = (struct commit_info *) xmalloc (sizeof (struct commit_info));
  499.         ci->status = status;
  500.         if (vers->tag)
  501.         if (isdigit (*vers->tag))
  502.             ci->rev = xstrdup (vers->tag);
  503.         else
  504.             ci->rev = RCS_whatbranch (file, vers->tag, srcfiles);
  505.         else
  506.         ci->rev = (char *) NULL;
  507.         ci->tag = xstrdup (vers->tag);
  508.         p->data = (char *) ci;
  509.         (void) addnode (cilist, p);
  510.         break;
  511.     case T_UNKNOWN:
  512.         error (0, 0, "nothing known about `%s'", file);
  513.         freevers_ts (&vers);
  514.         return (1);
  515.     case T_UPTODATE:
  516.         break;
  517.     default:
  518.         error (0, 0, "Unknown status 0x%x for `%s'", status, file);
  519.         break;
  520.     }
  521.  
  522.     freevers_ts (&vers);
  523.     return (0);
  524. }
  525.  
  526. /*
  527.  * Print warm fuzzies while examining the dirs
  528.  */
  529. /* ARGSUSED */
  530. static Dtype
  531. check_direntproc (dir, repos, update_dir)
  532.     char *dir;
  533.     char *repos;
  534.     char *update_dir;
  535. {
  536.     if (!quiet)
  537.     error (0, 0, "Examining %s", update_dir);
  538.  
  539.     return (R_PROCESS);
  540. }
  541.  
  542. /*
  543.  * Walklist proc to run pre-commit checks
  544.  */
  545. static int
  546. precommit_list_proc (p)
  547.     Node *p;
  548. {
  549.     if (p->data == (char *) T_ADDED || p->data == (char *) T_MODIFIED)
  550.     run_arg (p->key);
  551.     return (0);
  552. }
  553.  
  554. /*
  555.  * Callback proc for pre-commit checking
  556.  */
  557. static List *ulist;
  558. static int
  559. precommit_proc (repository, filter)
  560.     char *repository;
  561.     char *filter;
  562. {
  563.     /* see if the filter is there, only if it's a full path */
  564.     if (filter[0] == '/' && !isfile (filter))
  565.     {
  566.     error (0, errno, "cannot find pre-commit filter `%s'", filter);
  567.     return (1);            /* so it fails! */
  568.     }
  569.  
  570.     run_setup ("%s %s", filter, repository);
  571.     (void) walklist (ulist, precommit_list_proc);
  572.     return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
  573. }
  574.  
  575. /*
  576.  * Run the pre-commit checks for the dir
  577.  */
  578. /* ARGSUSED */
  579. static int
  580. check_filesdoneproc (err, repos, update_dir)
  581.     int err;
  582.     char *repos;
  583.     char *update_dir;
  584. {
  585.     int n;
  586.     Node *p;
  587.  
  588.     /* find the update list for this dir */
  589.     p = findnode (mulist, update_dir);
  590.     if (p != NULL)
  591.     ulist = ((struct master_lists *) p->data)->ulist;
  592.     else
  593.     ulist = (List *) NULL;
  594.  
  595.     /* skip the checks if there's nothing to do */
  596.     if (ulist == NULL || ulist->list->next == ulist->list)
  597.     return (err);
  598.  
  599.     /* run any pre-commit checks */
  600.     if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0)
  601.     {
  602.     error (0, 0, "Pre-commit check failed");
  603.     err += n;
  604.     }
  605.  
  606.     return (err);
  607. }
  608.  
  609. /*
  610.  * Do the work of committing a file
  611.  */
  612. static int maxrev;
  613. static char sbranch[PATH_MAX];
  614.  
  615. /* ARGSUSED */
  616. static int
  617. commit_fileproc (file, update_dir, repository, entries, srcfiles)
  618.     char *file;
  619.     char *update_dir;
  620.     char *repository;
  621.     List *entries;
  622.     List *srcfiles;
  623. {
  624.     Node *p;
  625.     int err = 0;
  626.     List *ulist, *cilist;
  627.     struct commit_info *ci;
  628.     char rcs[PATH_MAX];
  629.  
  630.     if (update_dir[0] == '\0')
  631.     p = findnode (mulist, ".");
  632.     else
  633.     p = findnode (mulist, update_dir);
  634.  
  635.     /*
  636.      * if p is null, there were file type command line args which were
  637.      * all up-to-date so nothing really needs to be done
  638.      */
  639.     if (p == NULL)
  640.     return (0);
  641.     ulist = ((struct master_lists *) p->data)->ulist;
  642.     cilist = ((struct master_lists *) p->data)->cilist;
  643.  
  644.     /*
  645.      * At this point, we should have the commit message unless we were called
  646.      * with files as args from the command line.  In that latter case, we
  647.      * need to get the commit message ourselves
  648.      */
  649.     if (use_editor && !got_message)
  650.     {
  651.     got_message = 1;
  652.     do_editor (update_dir, message, repository, ulist);
  653.     }
  654.  
  655.     p = findnode (cilist, file);
  656.     if (p == NULL)
  657.     return (0);
  658.  
  659.     ci = (struct commit_info *) p->data;
  660.     if (ci->status == T_MODIFIED)
  661.     {
  662.     if (lockrcsfile (file, repository, ci->rev) != 0)
  663.     {
  664.         unlockrcs (file, repository);
  665.         return (1);
  666.     }
  667.     }
  668.     else if (ci->status == T_ADDED)
  669.     {
  670.     if (checkaddfile (file, repository, ci->tag) != 0)
  671.     {
  672.         fixaddfile (file, repository);
  673.         return (1);
  674.     }
  675.     }
  676.  
  677.     /*
  678.      * Add the file for real
  679.      */
  680.     if (ci->status == T_ADDED)
  681.     {
  682.     char *xrev = (char *) NULL;
  683.  
  684.     if (ci->rev == NULL)
  685.     {
  686.         /* find the max major rev number in this directory */
  687.         maxrev = 0;
  688.         (void) walklist (entries, findmaxrev);
  689.         if (maxrev == 0)
  690.         maxrev = 1;
  691.         xrev = xmalloc (20);
  692.         (void) sprintf (xrev, "%d", maxrev);
  693.     }
  694.  
  695.     /* XXX - an added file with symbolic -r should add tag as well */
  696.     err = finaladd (file, ci->rev ? ci->rev : xrev, ci->tag,
  697.               repository, entries);
  698.     if (xrev)
  699.         free (xrev);
  700.     return (err);
  701.     }
  702.  
  703.     if (ci->status == T_MODIFIED)
  704.     {
  705.     locate_rcs (file, repository, rcs);
  706.     err = Checkin ('M', file, repository, rcs, ci->rev, ci->tag,
  707.                message, entries);
  708.     if (err != 0)
  709.     {
  710.         unlockrcs (file, repository);
  711.         fixbranch (file, repository, sbranch);
  712.     }
  713.     }
  714.  
  715.     if (ci->status == T_REMOVED)
  716.     err = remove_file (file, repository, ci->tag, entries);
  717.  
  718.     return (err);
  719. }
  720.  
  721. /*
  722.  * Log the commit and clean up the update list
  723.  */
  724. /* ARGSUSED */
  725. static int
  726. commit_filesdoneproc (err, repository, update_dir)
  727.     int err;
  728.     char *repository;
  729.     char *update_dir;
  730. {
  731.     List *ulist, *cilist;
  732.     char *xtag = (char *) NULL;
  733.     Node *p;
  734.  
  735.     p = findnode (mulist, update_dir);
  736.     if (p != NULL)
  737.     {
  738.     ulist = ((struct master_lists *) p->data)->ulist;
  739.     cilist = ((struct master_lists *) p->data)->cilist;
  740.     }
  741.     else
  742.     return (err);
  743.  
  744.     got_message = 0;
  745.  
  746.     /* see if we need to specify a per-directory or -r option tag */
  747.     if (tag == NULL)
  748.     ParseTag (&xtag, (char **) NULL);
  749.  
  750.     Update_Logfile (repository, message, tag ? tag : xtag, (FILE *) 0, ulist);
  751.     dellist (&ulist);
  752.     dellist (&cilist);
  753.     if (xtag)
  754.     free (xtag);
  755.  
  756.     if (err == 0 && run_module_prog)
  757.     {
  758.     char *cp;
  759.     FILE *fp;
  760.     char line[MAXLINELEN];
  761.     char *repository;
  762.  
  763.     /* It is not an error if Checkin.prog does not exist.  */
  764.     if ((fp = fopen (CVSADM_CIPROG, "r")) != NULL)
  765.     {
  766.         if (fgets (line, sizeof (line), fp) != NULL)
  767.         {
  768.         if ((cp = rindex (line, '\n')) != NULL)
  769.             *cp = '\0';
  770.         repository = Name_Repository ((char *) NULL, update_dir);
  771.         run_setup ("%s %s", line, repository);
  772.         (void) printf ("%s %s: Executing '", program_name,
  773.                    command_name);
  774.         run_print (stdout);
  775.         (void) printf ("'\n");
  776.         (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
  777.         free (repository);
  778.         }
  779.         (void) fclose (fp);
  780.     }
  781.     }
  782.  
  783.     return (err);
  784. }
  785.  
  786. /*
  787.  * Get the log message for a dir and print a warm fuzzy
  788.  */
  789. /* ARGSUSED */
  790. static Dtype
  791. commit_direntproc (dir, repos, update_dir)
  792.     char *dir;
  793.     char *repos;
  794.     char *update_dir;
  795. {
  796.     Node *p;
  797.     List *ulist;
  798.     char *real_repos;
  799.  
  800.     /* find the update list for this dir */
  801.     p = findnode (mulist, update_dir);
  802.     if (p != NULL)
  803.     ulist = ((struct master_lists *) p->data)->ulist;
  804.     else
  805.     ulist = (List *) NULL;
  806.  
  807.     /* skip the files as an optimization */
  808.     if (ulist == NULL || ulist->list->next == ulist->list)
  809.     return (R_SKIP_FILES);
  810.  
  811.     /* print the warm fuzzy */
  812.     if (!quiet)
  813.     error (0, 0, "Committing %s", update_dir);
  814.  
  815.     /* get commit message */
  816.     if (use_editor)
  817.     {
  818.     got_message = 1;
  819.     real_repos = Name_Repository (dir, update_dir);
  820.     do_editor (update_dir, message, real_repos, ulist);
  821.     free (real_repos);
  822.     }
  823.     return (R_PROCESS);
  824. }
  825.  
  826. /*
  827.  * Process the post-commit proc if necessary
  828.  */
  829. /* ARGSUSED */
  830. static int
  831. commit_dirleaveproc (dir, err, update_dir)
  832.     char *dir;
  833.     int err;
  834.     char *update_dir;
  835. {
  836.     /* update the per-directory tag info */
  837.     if (err == 0 && write_dirtag != NULL)
  838.     WriteTag ((char *) NULL, write_dirtag, (char *) NULL);
  839.  
  840.     return (err);
  841. }
  842.  
  843. /*
  844.  * find the maximum major rev number in an entries file
  845.  */
  846. static int
  847. findmaxrev (p)
  848.     Node *p;
  849. {
  850.     char *cp;
  851.     int thisrev;
  852.     Entnode *entdata;
  853.  
  854.     entdata = (Entnode *) p->data;
  855.     cp = index (entdata->version, '.');
  856.     if (cp != NULL)
  857.     *cp = '\0';
  858.     thisrev = atoi (entdata->version);
  859.     if (cp != NULL)
  860.     *cp = '.';
  861.     if (thisrev > maxrev)
  862.     maxrev = thisrev;
  863.     return (0);
  864. }
  865.  
  866. /*
  867.  * Actually remove a file by moving it to the attic
  868.  * XXX - if removing a ,v file that is a relative symbolic link to
  869.  * another ,v file, we probably should add a ".." component to the
  870.  * link to keep it relative after we move it into the attic.
  871.  */
  872. static int
  873. remove_file (file, repository, tag, entries)
  874.     char *file;
  875.     char *repository;
  876.     char *tag;
  877.     List *entries;
  878. {
  879.     int omask;
  880.     int retcode;
  881.     char rcs[PATH_MAX];
  882.     char tmp[PATH_MAX];
  883.  
  884.     locate_rcs (file, repository, rcs);
  885.     if (tag)
  886.     {
  887.     /* a symbolic tag is specified; just remove the tag from the file */
  888.     run_setup ("%s%s -q -N%s", Rcsbin, RCS, tag);
  889.     run_arg (rcs);
  890.     if ((retcode = run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL)) != 0)
  891.     {
  892.         if (!quiet)
  893.         error (0, retcode == -1 ? errno : 0,
  894.                "failed to remove tag `%s' from `%s'", tag, rcs);
  895.         return (1);
  896.     }
  897.     return (0);
  898.     }
  899.     else
  900.     {
  901.     /* no symbolic tag specified; really move it into the Attic */
  902.     (void) sprintf (tmp, "%s/%s", repository, CVSATTIC);
  903.     omask = umask (2);
  904.     (void) mkdir (tmp, 0777);
  905.     (void) umask (omask);
  906.     (void) sprintf (tmp, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
  907.  
  908.     if ((strcmp (rcs, tmp) == 0 || rename (rcs, tmp) != -1) ||
  909.         (!isreadable (rcs) && isreadable (tmp)))
  910.     {
  911.         Scratch_Entry (entries, file);
  912.         return (0);
  913.     }
  914.     }
  915.     return (1);
  916. }
  917.  
  918. /*
  919.  * Do the actual checkin for added files
  920.  */
  921. static int
  922. finaladd (file, rev, tag, repository, entries)
  923.     char *file;
  924.     char *rev;
  925.     char *tag;
  926.     char *repository;
  927.     List *entries;
  928. {
  929.     int ret;
  930.     char tmp[PATH_MAX];
  931.     char rcs[PATH_MAX];
  932.  
  933.     locate_rcs (file, repository, rcs);
  934.     ret = Checkin ('A', file, repository, rcs, rev, tag,
  935.            message, entries);
  936.     if (ret == 0)
  937.     {
  938.     (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_OPT);
  939.     (void) unlink_file (tmp);
  940.     (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
  941.     (void) unlink_file (tmp);
  942.     }
  943.     else
  944.     fixaddfile (file, repository);
  945.     return (ret);
  946. }
  947.  
  948. /*
  949.  * Unlock an rcs file
  950.  */
  951. static void
  952. unlockrcs (file, repository)
  953.     char *file;
  954.     char *repository;
  955. {
  956.     char rcs[PATH_MAX];
  957.     int retcode = 0;
  958.  
  959.     locate_rcs (file, repository, rcs);
  960.     run_setup ("%s%s -q -u", Rcsbin, RCS);
  961.     run_arg (rcs);
  962.  
  963.     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
  964.     error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
  965.            "could not unlock %s", rcs);
  966. }
  967.  
  968. /*
  969.  * remove a partially added file.  if we can parse it, leave it alone.
  970.  */
  971. static void
  972. fixaddfile (file, repository)
  973.     char *file;
  974.     char *repository;
  975. {
  976.     RCSNode *rcsfile;
  977.     char rcs[PATH_MAX];
  978.     int save_really_quiet;
  979.  
  980.     locate_rcs (file, repository, rcs);
  981.     save_really_quiet = really_quiet;
  982.     really_quiet = 1;
  983.     if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
  984.     (void) unlink_file (rcs);
  985.     else
  986.     freercsnode (&rcsfile);
  987.     really_quiet = save_really_quiet;
  988. }
  989.  
  990. /*
  991.  * put the branch back on an rcs file
  992.  */
  993. static void
  994. fixbranch (file, repository, branch)
  995.     char *file;
  996.     char *repository;
  997.     char *branch;
  998. {
  999.     char rcs[PATH_MAX];
  1000.     int retcode = 0;
  1001.  
  1002.     if (branch != NULL && branch[0] != '\0')
  1003.     {
  1004.     locate_rcs (file, repository, rcs);
  1005.     run_setup ("%s%s -q -b%s", Rcsbin, RCS, branch);
  1006.     run_arg (rcs);
  1007.     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
  1008.         error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
  1009.            "cannot restore branch to %s for %s", branch, rcs);
  1010.     }
  1011. }
  1012.  
  1013. /*
  1014.  * do the initial part of a file add for the named file.  if adding
  1015.  * with a tag, put the file in the Attic and point the symbolic tag
  1016.  * at the committed revision.
  1017.  */
  1018. static int
  1019. checkaddfile (file, repository, tag)
  1020.     char *file;
  1021.     char *repository;
  1022.     char *tag;
  1023. {
  1024.     FILE *fp;
  1025.     char *cp;
  1026.     char rcs[PATH_MAX];
  1027.     char fname[PATH_MAX];
  1028.     int omask;
  1029.     int retcode = 0;
  1030.  
  1031.     if (tag)
  1032.     {
  1033.     (void) sprintf(rcs, "%s/%s", repository, CVSATTIC);
  1034.     omask = umask (2);
  1035.     (void) mkdir (rcs, 0777);
  1036.     (void) umask (omask);
  1037.     (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
  1038.     }
  1039.     else
  1040.     locate_rcs (file, repository, rcs);
  1041.  
  1042.     run_setup ("%s%s -i", Rcsbin, RCS);
  1043.     run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG);
  1044.     (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT);
  1045.     fp = open_file (fname, "r");
  1046.     while (fgets (fname, sizeof (fname), fp) != NULL)
  1047.     {
  1048.     if ((cp = rindex (fname, '\n')) != NULL)
  1049.         *cp = '\0';
  1050.     if (*fname)
  1051.         run_arg (fname);
  1052.     }
  1053.     (void) fclose (fp);
  1054.     run_arg (rcs);
  1055.     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
  1056.     {
  1057.     error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
  1058.            "could not create %s", rcs);
  1059.     return (1);
  1060.     }
  1061.     fix_rcs_modes (rcs, file);
  1062.     return (0);
  1063. }
  1064.  
  1065. /*
  1066.  * Lock the rcs file ``file''
  1067.  */
  1068. static int
  1069. lockrcsfile (file, repository, rev)
  1070.     char *file;
  1071.     char *repository;
  1072.     char *rev;
  1073. {
  1074.     char rcs[PATH_MAX];
  1075.  
  1076.     locate_rcs (file, repository, rcs);
  1077.     if (lock_RCS (file, rcs, rev, repository) != 0)
  1078.     return (1);
  1079.     else
  1080.     return (0);
  1081. }
  1082.  
  1083. /*
  1084.  * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it
  1085.  * couldn't.  If the RCS file currently has a branch as the head, we must
  1086.  * move the head back to the trunk before locking the file, and be sure to
  1087.  * put the branch back as the head if there are any errors.
  1088.  */
  1089. static int
  1090. lock_RCS (user, rcs, rev, repository)
  1091.     char *user;
  1092.     char *rcs;
  1093.     char *rev;
  1094.     char *repository;
  1095. {
  1096.     RCSNode *rcsfile;
  1097.     char *branch = NULL;
  1098.     int err = 0;
  1099.  
  1100.     /*
  1101.      * For a specified, numeric revision of the form "1" or "1.1", (or when
  1102.      * no revision is specified ""), definitely move the branch to the trunk
  1103.      * before locking the RCS file.
  1104.      * 
  1105.      * The assumption is that if there is more than one revision on the trunk,
  1106.      * the head points to the trunk, not a branch... and as such, it's not
  1107.      * necessary to move the head in this case.
  1108.      */
  1109.     if (rev == NULL || (rev && isdigit (*rev) && numdots (rev) < 2))
  1110.     {
  1111.     if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
  1112.     {
  1113.         /* invalid rcs file? */
  1114.         err = 1;
  1115.     }
  1116.     else
  1117.     {
  1118.         /* rcsfile is valid */
  1119.         branch = xstrdup (rcsfile->branch);
  1120.         freercsnode (&rcsfile);
  1121.         if (branch != NULL)
  1122.         {
  1123.         run_setup ("%s%s -q -b", Rcsbin, RCS);
  1124.         run_arg (rcs);
  1125.         if (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) != 0)
  1126.         {
  1127.             error (0, 0, "cannot change branch to default for %s",
  1128.                rcs);
  1129.             if (branch)
  1130.             free (branch);
  1131.             return (1);
  1132.         }
  1133.         }
  1134.         run_setup ("%s%s -q -l", Rcsbin, RCS);
  1135.         run_arg (rcs);
  1136.         err = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
  1137.     }
  1138.     }
  1139.     else
  1140.     {
  1141.     run_setup ("%s%s -q -l%s", Rcsbin, RCS, rev ? rev : "");
  1142.     run_arg (rcs);
  1143.     (void) run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL);
  1144.     }
  1145.  
  1146.     if (err == 0)
  1147.     {
  1148.     if (branch)
  1149.     {
  1150.         (void) strcpy (sbranch, branch);
  1151.         free (branch);
  1152.     }
  1153.     else
  1154.         sbranch[0] = '\0';
  1155.     return (0);
  1156.     }
  1157.  
  1158.     /* try to restore the branch if we can on error */
  1159.     if (branch != NULL)
  1160.     fixbranch (user, repository, branch);
  1161.  
  1162.     if (branch)
  1163.     free (branch);
  1164.     return (1);
  1165. }
  1166.  
  1167. /*
  1168.  * Called when "add"ing files to the RCS respository, as it is necessary to
  1169.  * preserve the file modes in the same fashion that RCS does.  This would be
  1170.  * automatic except that we are placing the RCS ,v file very far away from
  1171.  * the user file, and I can't seem to convince RCS of the location of the
  1172.  * user file.  So we munge it here, after the ,v file has been successfully
  1173.  * initialized with "rcs -i".
  1174.  */
  1175. static void
  1176. fix_rcs_modes (rcs, user)
  1177.     char *rcs;
  1178.     char *user;
  1179. {
  1180.     struct stat sb;
  1181.  
  1182.     if (stat (user, &sb) != -1)
  1183.     (void) chmod (rcs, (int) sb.st_mode & ~0222);
  1184. }
  1185.  
  1186. /*
  1187.  * free an UPDATE node's data (really nothing to do)
  1188.  */
  1189. void
  1190. update_delproc (p)
  1191.     Node *p;
  1192. {
  1193.     p->data = (char *) NULL;
  1194. }
  1195.  
  1196. /*
  1197.  * Free the commit_info structure in p.
  1198.  */
  1199. static void
  1200. ci_delproc (p)
  1201.     Node *p;
  1202. {
  1203.     struct commit_info *ci;
  1204.  
  1205.     ci = (struct commit_info *) p->data;
  1206.     if (ci->rev)
  1207.     free (ci->rev);
  1208.     if (ci->tag)
  1209.     free (ci->tag);
  1210.     free (ci);
  1211. }
  1212.  
  1213. /*
  1214.  * Find an RCS file in the repository.
  1215.  */
  1216. static void
  1217. locate_rcs (file, repository, rcs)
  1218.     char *file;
  1219.     char *repository;
  1220.     char *rcs;
  1221. {
  1222.     (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
  1223.     if (!isreadable (rcs))
  1224.     {
  1225.     (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
  1226.     if (!isreadable (rcs))
  1227.         (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
  1228.     }
  1229. }
  1230.